#include "drawer.h"
#include "getwindow.h"
#include "logging.hpp"
#include "screenbuffer.h"
#include <cmath>
#include <cstring>
#include <stdexcept>
#include <string.h>
#include <wingdi.h>

Drawer::Drawer(ScreenBuffer_t* _ptr_BaseBuffer) : BaseActor(_ptr_BaseBuffer) {}

Drawer::~Drawer() {}

// if your interface is bigger than the your screen,
// due to the machanic limit, the vertical out-bound signs would be cutout,
// But the horizonal out-bound signs will not.
// Use this function to limit the index into the valid range.
// .^.
// [index]       is the GlobalCoord,
// [Xsize]       is the max Xsize that your element Xsize on this line would be,
// [windowXsize] should be acquired through ScreenBuffer,
// [filled]      should be acquired through drawer method input.
inline Action_t _edgebreak(int& index, short& Xsize, short& windowXsize, bool& filled)
{
    if (index + Xsize + !(filled) > windowXsize)
    {
        Xsize = short(windowXsize - index - !(filled));
    }
    return 0;
}

//------------------------------ line begin ------------------------------
// A independent line defination:
Action_t Drawer::Xline(const X_lineAttr& Attributes)
{
    return this->Xline(Attributes.beginpos, Attributes.drawchar, Attributes.drawchar);
}
Action_t Drawer::Xline(const Coord_t& pos, short length, char drawchar)
{
    int Globalcursor;
    // return 255;
    if (length >= ptr_BaseBuffer->_WindowSize.sqaure() || length <= 0)
    {
        Addlog(LOG::logType::Error, "A Invalid Shape was Drawed. Drawer::Xline");
        text(pos, "Failed to render Xline", {22, 1}, false);
        return -1;
    }
    _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, Globalcursor);

    if (memset(&buffer[Globalcursor], drawchar, length))
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

// instantiation of template
// since this template only accpet this three type
template Action_t Drawer::line<LinePointAttr>(const LinePointAttr& Attributes);
template Action_t Drawer::line<LineVectorAttr>(const LineVectorAttr& Attributes);
template Action_t Drawer::line<LineDegreeAttr>(const LineDegreeAttr& Attributes);

// Notice that this template only accpet three types:.
// LinePointAttr, LineVectorAttr, LineDegreeAttr.
// They are three defination of LineSection.
// This template method would use the proper way to draw a line.
template <typename TypeAttr> Action_t Drawer::line(const TypeAttr& Attributes)
{
    Coord_t cursorLocal;
    int cursorGlobal;
    const Coord_t& beginpos{Attributes.beginpos};
    const Coord_t& Edge{ptr_BaseBuffer->_WindowSize};
    const char& drawchar{Attributes.drawchar};
    int Length{ptr_BaseBuffer->_WindowSize.X};
    short& x = cursorLocal.X;
    short& y = cursorLocal.Y;
    double k{1.000000};
    bool isVerical{false};

    if constexpr (std::is_same_v<TypeAttr, LineVectorAttr>)
    {
        if (Attributes.direction.X == 0)
        {
            isVerical = true;
            Length = Attributes.length;
        }
        else
        {
            k = k * Attributes.direction.Y / Attributes.direction.X;
            Length = beginpos.X + Attributes.length;
        }
    }
    else if constexpr (std::is_same_v<TypeAttr, LineDegreeAttr>)
    {

        if (Attributes.degree == 90 || Attributes.degree == -90)
        {
            isVerical = true;
            Length = Attributes.length;
        }
        else
        {
            k = tan(Attributes.degree * (M_PI / 180.0));
            Length = int(Attributes.length / cos(Attributes.degree * (M_PI / 180.0))) + beginpos.X;
        }
    }
    else if constexpr (std::is_same_v<TypeAttr, LinePointAttr>)
    {
        const Coord_t& endpos{Attributes.endpos};
        if (endpos.X == beginpos.X)
        {
            isVerical = true;
            Length = endpos.Y - beginpos.Y;
        }
        else
        {
            k = k * (endpos.Y - beginpos.Y) / (endpos.X - beginpos.X);
            Length = int(endpos.X);
        }
    }
    else
    {
        static_assert(true,
                      "Only Support Attrtype that relevent to Lines: Use LinePointAttr,LineDegreeAttr,LineVectorAttr");
    }
    if (isVerical)
        for (y = beginpos.Y; y < Length + beginpos.Y && y < Edge.Y; y++)
        {
            x = beginpos.X;
            _CoordInterp(cursorLocal, ptr_BaseBuffer->_WindowSize, cursorGlobal);
            buffer[cursorGlobal] = drawchar;
        }
    else
        for (x = beginpos.X; x < Length && x < Edge.X; x++)
        {
            y = short(round(k * (x - beginpos.X) + beginpos.Y));
            _CoordInterp(cursorLocal, ptr_BaseBuffer->_WindowSize, cursorGlobal);
            buffer[cursorGlobal] = drawchar;
        }
    return 1;
}
//------------------------------ line ends ------------------------------

Action_t Drawer::Square(const Coord_t& Pos, const Coord_t& Size, char head, char side, char corner = '+',
                        bool filled = false)
{
    if (Size.X <= 1 || Size.Y <= 1)
    {
        Addlog(LOG::logType::Error, "A Invalid size Shape was Drawed. Drawer::Square");
        text(Pos, "X <- [ErrorSquare]", {18, 1}, false);
        return -1;
    }
    // Aware: "global" is for "global coord", instead of "global functioning"
    Coord_t pos{Pos};
    Coord_t ActualSize{short(Size.X - 1), short(Size.Y - 1)};
    int pos_in_global;
    _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
    if (pos_in_global < 0)
    {
        Addlog(LOG::logType::Error, "A Invalid pos Shape was Drawed. Drawer::Square");
        text({0, 0}, "X <- [ErrorSquare]", {18, 1}, false);
        return -1;
    }

    //_edgebreak(pos_in_global, ActualSize.X, ptr_BaseBuffer->_WindowSize.X, filled);
    ptr_BaseBuffer->Fill(buffer + pos_in_global, buffer + pos_in_global + ActualSize.X + 1, head);
    if (!filled)
    {
        buffer[pos_in_global] = corner;
        buffer[pos_in_global + ActualSize.X] = corner;
    }
    for (int y = 0; y < ActualSize.Y; y++)
    {
        pos.Y += 1;
        _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
        if (filled)
        {
            ptr_BaseBuffer->Fill(buffer + pos_in_global + 1, buffer + pos_in_global + ActualSize.X, side);
        }
        buffer[pos_in_global] = side;
        buffer[pos_in_global + ActualSize.X] = side;
    }

    _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
    ptr_BaseBuffer->Fill(buffer + pos_in_global, buffer + pos_in_global + ActualSize.X + 1, head);
    if (!filled)
    {
        buffer[pos_in_global] = corner;
        buffer[pos_in_global + ActualSize.X] = corner;
    }
    return 1;
}

Action_t Drawer::Square(Coord_t Pos, Coord_t Size, char Drawchar, bool Filled)
{
    return Square(Pos, Size, Drawchar, Drawchar, Filled);
}
Action_t Drawer::Square(const ShapeAttr& _Attr)
{
    return Square(_Attr.pos, _Attr.size, _Attr.drawchar, _Attr.filled);
}

#define CirclePos(a, b, x, y, posx, posy) (y - posy) * (y - posy) == (b / a) * (a * a - (x - posx) * (x - posx))

Action_t Drawer::Circle(const ShapeAttr& _Attr)
{
    Coord_t Localcursor;
    const Coord_t& pos{_Attr.pos};
    const Coord_t& size{_Attr.size};
    const char& drawchar{_Attr.drawchar};
    const bool& filled{_Attr.filled};
    int Globalcursor;
    short& x{Localcursor.X};
    short& y{Localcursor.Y};
    double e = 1.0000000 * size.Y / size.X;
    short* sq_1 = new short[size.X / 2 + 1];
    short* sq_2 = new short[size.X / 2 + 1];
    // if(!filled)
    x = pos.X;
    for (int i = 0; i <= size.X / 2; i++, x++)
    {
        y = short(round(e * sqrt(x - pos.X) + pos.Y));
        sq_1[i] = y;
        _CoordInterp(Localcursor, ptr_BaseBuffer->_WindowSize, Globalcursor);
        buffer[Globalcursor] = drawchar;

        y = short(round(-e * sqrt(x - pos.X) + pos.Y));
        sq_2[i] = y;
        _CoordInterp(Localcursor, ptr_BaseBuffer->_WindowSize, Globalcursor);
        buffer[Globalcursor] = drawchar;
    }
    for (int i = 0; i <= size.X / 2; i++, x++)
    {
        y = sq_1[size.X / 2 - i];
        _CoordInterp(Localcursor, ptr_BaseBuffer->_WindowSize, Globalcursor);
        buffer[Globalcursor] = drawchar;
        y = sq_2[size.X / 2 - i];
        _CoordInterp(Localcursor, ptr_BaseBuffer->_WindowSize, Globalcursor);
        buffer[Globalcursor] = drawchar;
    }
    delete[] sq_1;
    delete[] sq_2;

    return 0;
}

#ifndef __stdstring_in_using__
Action_t Drawer::text(const TextAttr& _Attr)
{
    const Coord_t& pos{_Attr.pos};
    short size{static_cast<short>(_Attr.content.length())};
    if (_Attr.content.length() == 0) return -1;
    int pos_in_global;
    bool filled{false};
    _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
    _edgebreak(pos_in_global, size, ptr_BaseBuffer->_WindowSize.X, filled);
    for (int i = 0; i < _Attr.content.length() && _Attr.content[i] != '\0'; i++)
    {
        buffer[pos_in_global + i] = _Attr.content[i];
    }
    return 0;
}
#else
Action_t Drawer::text(Coord_t pos, const String_t& string, Coord_t renderSize, bool rollAllowed) const
{
    // return 255;
    // if (!string.data())
    // {
    //     Addlog(LOG::logType::Error, "A Invalid Text was Drawed. Drawer::Text");
    //     text({5, 5}, "X <== [ErrorText]", {18, 1}, false);
    //     return -1;
    // }
    if (renderSize.X <= 0 || renderSize.Y <= 0)
    {
        Addlog(LOG::logType::Error, "A Invalid Text was Drawed. Drawer::Text");
        text(pos, "X <== [ErrorText]", {18, 1}, false);
        return -1;
    }
    Coord_t Cursor{pos};
    short size{static_cast<short>(string.length())};
    int pos_in_global;
    bool filled{false};
    int index{0};
    int LengthInQueue = string.length(); // how many chars are waiting to be filled in

    if (LengthInQueue == 0) // Empty String
        return -1;
    else if (LengthInQueue <= renderSize.X) // String is shorter than renderLimit
    {
        _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
        memmove(&buffer[pos_in_global], &string[0], string.length());
    }
    else // String is Longer than renderLimit, need to be cut off
    {
        int temp{0};
        // renderSize.X += string.length();
        _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
        // TODO: Need add the support of "\n"
        do
        {
            // memmove(&buffer[pos_in_global], &string[string.length() - LengthInQueue], renderSize.X);
            memmove(&buffer[pos_in_global], &string[string.length() - LengthInQueue], renderSize.X);
            LengthInQueue -= renderSize.X;
            pos.Y++; // TODO: 划词换行 May Realize it in another text type
            _CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
            if (pos.Y - Cursor.Y > renderSize.Y)
            {
                return 1;
            }
        } while (LengthInQueue >= renderSize.X);
        //_CoordInterp(pos, ptr_BaseBuffer->_WindowSize, pos_in_global);
        memmove(&buffer[pos_in_global], &string[string.length() - LengthInQueue], LengthInQueue);
        // error : wrong postion to cut off

        // FIXED : Text would went out of the renderLimit in the last line;
        // Original Circumstance: Original use memmove
        //              Warning : May cause ERROR since renderSize.X could be bigger that the last LengthInQueue;

        //     Modified solution: Since We temprorily using std::string, it will automaticly add '\0' at last;
        //                        We use memccpy to stop copy at '\0' pos;
    }
    return 0;
}
Action_t Drawer::text(const TextAttr& _Attr) const
{
    return 0; // this->text(_Attr.pos, _Attr.content, _Attr.rendersize, _Attr.rollAllowed);
}
// Coord_t pos
// String_t content;
// Coord_t rendersize;
// bool rollAllowed{false};

#endif